// monitor.c -- Defines functions for writing to the monitor.
//             heavily based on Bran's kernel development tutorials,
//             but rewritten for JamesM's kernel tutorials.

#include <monitor.h>

// The VGA framebuffer starts at 0xB8000.
u16int *video_memory = (u16int *)0xB8000;
// Stores the cursor position.
u8int cursor_x = 0;
u8int cursor_y = 0;
u8int backColour = 0;
u8int foreColour = 15; 

// Updates the hardware cursor.
static void move_cursor()
{
    // The screen is 80 characters wide...
    u16int cursorLocation = cursor_y * 80 + cursor_x;
    outb(0x3D4, 14);                  // Tell the VGA board we are setting the high cursor byte.
    outb(0x3D5, cursorLocation >> 8); // Send the high cursor byte.
    outb(0x3D4, 15);                  // Tell the VGA board we are setting the low cursor byte.
    outb(0x3D5, cursorLocation);      // Send the low cursor byte.
}

// Scrolls the text on the screen up by one line.
static void scroll()
{

    // Get a space character with the default colour attributes.
	u8int attributeByte = (backColour /*black*/ << 4) | (foreColour /*white*/ & 0x0F);
    u16int blank = 0x20 /* space */ | (attributeByte << 8);

    // Row 25 is the end, this means we need to scroll up
    if(cursor_y >= 50)
    {
        // Move the current text chunk that makes up the screen
        // back in the buffer by a line
        int i;
        for (i = 0*80; i < 49*80; i++)
        {
            video_memory[i] = video_memory[i+80];
        }

        // The last line should now be blank. Do this by writing
        // 80 spaces to it.
        for (i = 49*80; i < 25*80; i++)
        {
            video_memory[i] = blank;
        }
        // The cursor should now be on the last line.
        cursor_y = 49;
    }
}

// Clears the screen, by copying lots of spaces to the framebuffer.
void monitor_clear()
{
    // Make an attribute byte for the default colours
	u8int attributeByte = (backColour /*black*/ << 4) | (foreColour /*white*/ & 0x0F);
    u16int blank = 0x20 /* space */ | (attributeByte << 8);

    int i;
    for (i = 0; i < 80*50; i++)
    {
        video_memory[i] = blank;
    }

    // Move the hardware cursor back to the start.
    cursor_x = 0;
    cursor_y = 0;
    move_cursor();
}

// Outputs a null-terminated ASCII string to the monitor.
void putchar (int c)
{
   // The attribute byte is made up of two nibbles - the lower being the
   // foreground colour, and the upper the background colour.
   unsigned char attributeByte = (backColour << 4) | (foreColour & 0x0F);
   // The attribute byte is the top 8 bits of the word we have to send to the
   // VGA board.
   unsigned short attribute = attributeByte << 8;
   unsigned short *location;

   // Handle a backspace, by moving the cursor back one space
   if (c == 0x08 && cursor_x)
   {
           cursor_x--;
   }

   // Handle a tab by increasing the cursor's X, but only to a point
   // where it is divisible by 8.
   else if (c == 0x09)
   {
       cursor_x = (cursor_x+8) & ~(8-1);
   }

   // Handle carriage return
   else if (c == '\r')
   {
       cursor_x = 0;
   }

   // Handle newline by moving cursor back to left and increasing the row
   else if (c == '\n')
   {
       cursor_x = 0;
       cursor_y++;
   }
   // Handle any other printable character.
   else if(c >= ' ')
   {
       location = video_memory + (cursor_y*80 + cursor_x);
       *location = c | attribute;
       cursor_x++;
   }

   // Check if we need to insert a new line because we have reached the end
   // of the screen.
   if (cursor_x >= 80)
   {
       cursor_x = 0;
       cursor_y ++;
   }

   // Scroll the screen if needed.
   scroll();
   move_cursor();
}

void itoa (char *buf, int base, int d)
{
        char *p = buf;
        char *p1, *p2;
        unsigned long ud = d;
        int divisor = 10;
     
        /* If %d is specified and D is minus, put `-' in the head. */
        if (base == 'd' && d < 0)
        {
                *p++ = '-';
                buf++;
                ud = -d;
        }
        else if (base == 'x')
                divisor = 16;
     
        /* Divide UD by DIVISOR until UD == 0. */
        do
        {
                int remainder = ud % divisor;
     
                *p++ = (remainder < 10) ? remainder + '0' : remainder + 'a' - 10;
        }
        while (ud /= divisor);
     
        /* Terminate BUF. */
        *p = 0;
     
        /* Reverse BUF. */
        p1 = buf;
        p2 = p - 1;
        while (p1 < p2)
        {
                char tmp = *p1;
                *p1 = *p2;
                *p2 = tmp;
                p1++;
                p2--;
        }

        scroll();
        move_cursor();
}

void printf (const char *format, ...)
{
        char **arg = (char **) &format;
        int c;
        char buf[20];
     
        arg++;
     
        while ((c = *format++) != 0)
        {
                if (c != '%')
                        putchar (c);
                else
                {
                        char *p;
     
                        c = *format++;
                        switch (c)
                        {
                                case 'd':
                                case 'u':
                case 'x':
                                        itoa (buf, c, *((int *) arg++));
                                        p = buf;
                                        goto string;
                                        break;
     
                case 's':
                                        p = *arg++;
                                        if (! p)
                                                p = "(null)";
     
                                        string:
                                        while (*p)
                                                putchar (*p++);
                                        break;
     
                 default:
                                        putchar (*((int *) arg++));
                                        break;
                        }
                }
        }
}

void change_colors(int back, int forground)
{
	backColour = back;
	foreColour = forground;
}